package handle

import (
	"GoApi/global"
	"GoApi/handle/contract"
	"GoApi/model"
	"GoApi/utils"
	"GoApi/utils/ether"
	"context"
	"fmt"
	"log"
	"math/big"
	"strconv"
	"strings"
	"time"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/labstack/echo/v4"
)

// @Summary     生成跨链任务
// @Description 生成跨链任务
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Param       payorderid      query    string true "跨链订单编号"
// @Param       amountin        query    string true "收到金额"
// @Param       amountout       query    string true "支出金额"
// @Param       srcchainid      query    string true "源链ID"
// @Param       dstchainid      query    string true "目标链ID"
// @Param       tokenin         query    string true "收到币种"
// @Param       tokenout        query    string true "支持币种"
// @Param       sender          query    string true "发送地址"
// @Param       receiver        query    string true "收币地址"
// @Param       node            query    string true "节点地址"
// @Param       srchash         query    string true "源链交易哈希"
// @Param       chargefee       query    string true "交易手续费"
// @Param       swapdstchaingas query    string true "兑换目标链gas"
// @Success     200             {object} string
// @Failure     201             {object} string
// @Router      /crosschain/createhytask [post]
func CreateHyTask(c echo.Context) error {
	if c.FormValue("payorderid") == "" {
		return utils.ErrParamType(c)
	}
	existsPayorderid, err := global.RD.GetString(c.FormValue("payorderid"))
	if err == nil && existsPayorderid == "true" {
		global.Log.Error("====>  redis GetString EvmOrderInfo_ error： %s", err.Error())
		return utils.ErrorNull(c, "payorderid has used")
	}
	if c.FormValue("srchash") == "" {
		return utils.ErrParamType(c)
	}
	// _, err = strconv.Atoi(c.FormValue("amountin"))
	// if err != nil {
	// 	return utils.ErrParamType(c)
	// }
	// _, err = strconv.Atoi(c.FormValue("amountout"))
	// if err != nil {
	// 	return utils.ErrParamType(c)
	// }
	srcChainID, err := strconv.Atoi(c.FormValue("srcchainid"))
	if err != nil {
		return utils.ErrParamType(c)
	}
	dstChainID, err := strconv.Atoi(c.FormValue("dstchainid"))
	if err != nil {
		return utils.ErrParamType(c)
	}
	// if !common.IsHexAddress(c.FormValue("tokenin")) {
	// 	return utils.ErrParamType(c)
	// }
	// if !common.IsHexAddress(c.FormValue("tokenout")) {
	// 	return utils.ErrParamType(c)
	// }
	// if !common.IsHexAddress(c.FormValue("receiver")) {
	// 	return utils.ErrParamType(c)
	// }
	// if !common.IsHexAddress(c.FormValue("node")) {
	// 	return utils.ErrParamType(c)
	// }
	_, err = strconv.ParseFloat(c.FormValue("chargefee"), 64)
	if err != nil {
		return utils.ErrParamType(c)
	}
	var hyBridgeOrder model.HybridgeOrder
	hyBridgeOrder.Node = c.FormValue("node")
	hyBridgeOrder.PayorderID = c.FormValue("payorderid")
	hyBridgeOrder.AmountIn = c.FormValue("amountin")
	hyBridgeOrder.AmountOut = c.FormValue("amountout")
	hyBridgeOrder.TokenIn = c.FormValue("tokenin")
	hyBridgeOrder.TokenOut = c.FormValue("tokenout")
	hyBridgeOrder.Sender = c.FormValue("sender")
	hyBridgeOrder.Receiver = c.FormValue("receiver")
	hyBridgeOrder.SrcHash = c.FormValue("srchash")
	hyBridgeOrder.State = model.WaitBegin
	hyBridgeOrder.SrcChainID = srcChainID
	hyBridgeOrder.DstChainID = dstChainID
	hyBridgeOrder.Network = global.GetNewWorkInfo(srcChainID).Name + "-" + global.GetNewWorkInfo(dstChainID).Name
	hyBridgeOrder.ChargeFee = c.FormValue("chargefee")
	tokenInfo := global.GetTokenInfo(srcChainID, c.FormValue("tokenin"))
	if tokenInfo != nil && tokenInfo.ChainID > 0 {
		hyBridgeOrder.Coin = global.GetTokenInfo(srcChainID, c.FormValue("tokenin")).Symbol
	} else {
		hyBridgeOrder.Coin = "NFT"
	}
	hyBridgeOrder.Type = "0"
	if hyBridgeOrder.SrcChainID == model.CosmosChainID || hyBridgeOrder.DstChainID == model.CosmosChainID {
		hyBridgeOrder.Type = "1"
	}
	if hyBridgeOrder.Coin == "NFT" {
		hyBridgeOrder.Type = "2"
	}
	if hyBridgeOrder.DstChainID != model.CosmosChainID {
		hyBridgeOrder.GasFee = strconv.FormatFloat(getChainGasFee(dstChainID), 'g', 5, 32)
	}
	if c.FormValue("swapdstchaingas") != "" {
		_, err := strconv.ParseFloat(c.FormValue("swapdstchaingas"), 64)
		if err != nil {
			return utils.ErrParamType(c)
		}
		hyBridgeOrder.SwapDstChainGas = c.FormValue("swapdstchaingas")
	}

	hyBridgeOrder.InsertTime = time.Now()
	hyBridgeOrder.LockTimeStamp = "0"

	// payorderID := makePayOrderId(big.NewInt(time.Now().Unix()), uint32(time.Now().Unix()+3000), uint32(srcChainId), uint32(dstChainId), 3, 1)
	if err := global.DB.Create(&hyBridgeOrder).Error; err != nil {
		global.Log.Error(" sql Create CreateOrder error：%v", err.Error())
		return utils.ErrorNull(c, "create fail please retry again")
	}
	return utils.SuccessNull(c, "create success")
}

// @Summary     更新任务信息
// @Description 更新任务信息
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Param       taskid  query    string true "任务编号"
// @Param       state   query    string true "状态"
// @Param       dsthash query    string true "哈希"
// @Success     200     {object} string
// @Failure     201 {object} string
// @Router      /crosschain/updatehytask [post]
func UpdateHyTask(c echo.Context) error {
	taskId := c.FormValue("taskid")

	var hyBridgeOrder model.HybridgeOrder
	if err := global.DB.Model(model.HybridgeOrder{}).Where(" payorder_id = ? ", taskId).Find(&hyBridgeOrder).Error; err == nil && hyBridgeOrder.ID > 0 {
		hyBridgeOrder.DstHash = c.FormValue("dsthash")
		hyBridgeOrder.State = c.FormValue("state")
		if c.FormValue("state") == "0" {
			hyBridgeOrder.LockTimeStamp = "0"
		}

		global.DB.Save(&hyBridgeOrder)
		return utils.SuccessNull(c, "update success")
	} else {
		return utils.ErrorNull(c, "update fail please retry again")
	}
}

// @Summary     获取EVM链跨链任务
// @Description 获取EVM链跨链任务
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Param       bot       query    string true "bot地址"
// @Param       timeStamp  query    string true "时间戳"
// @Param       sign       query    string true "签名"
// @Success     200        {object} []model.HybridgeOrder
// @Failure     201       {object} string
// @Router      /crosschain/getcrosschaintask [post]
func GetCrossChaintask(c echo.Context) error {
	if !common.IsHexAddress(c.FormValue("bot")) {
		return utils.ErrParamType(c)
	}
	signStr := "getcrosschaintask timestamp:" + c.FormValue("timeStamp")
	if len(c.FormValue("sign")) != 130 || !verifySign(signStr, c.FormValue("sign")) {
		return utils.SignValidFail(c)
	}
	var hyBridgeOrder model.HybridgeOrder
	if err := global.DB.Model(model.HybridgeOrder{}).Where(" state = 0 and lock_time_stamp = 0 and payorder_id not in (?)", getHandingEvmTask()).Find(&hyBridgeOrder).Error; err == nil && hyBridgeOrder.ID > 0 {
		// key := model.RDPrefixEvmOrder + hyBridgeOrder.PayorderID
		// if _, err := global.RD.SetAndExpire(key, hyBridgeOrder.PayorderID, 3000); err != nil {
		// 	global.Log.Error("====>  redis SetString key_[%v] error：%v", key, err)
		// }
		// global.Log.Info("====>  getcrosschaintask set key_[%v] success", key)
		hyBridgeOrder.LockTimeStamp = strconv.Itoa(int(time.Now().Unix()) + 3000)
		global.DB.Save(&hyBridgeOrder)

		return utils.SuccessNullMsg(c, hyBridgeOrder)
	} else {
		return utils.Success(c, "not any evmchain croos chain task", nil)
	}
}

// @Summary     获取COSMOS链跨链任务
// @Description 获取COSMOS链跨链任务
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Param       bot       query    string true "bot地址"
// @Param       timeStamp query    string true "时间戳"
// @Param       sign      query    string true "签名"
// @Success     200     {object} []model.HybridgeOrder
// @Failure     201       {object} string
// @Router      /crosschain/getcosmoschaintask [post]
func GetCosmosChainTask(c echo.Context) error {
	if !common.IsHexAddress(c.FormValue("botadr")) {
		return utils.ErrParamType(c)
	}
	signStr := "getcrosschaintask timeStamp:" + c.FormValue("timeStamp")
	if len(c.FormValue("sign")) != 130 || !verifySign(signStr, c.FormValue("sign")) {
		return utils.SignValidFail(c)
	}
	var hyBridgeOrder model.HybridgeOrder
	if err := global.DB.Model(model.HybridgeOrder{}).Where(" type = 1 and state = 0 and lock_time_stamp = 0 and payorderid not in (?)", getHandingCosmosTask()).Find(&hyBridgeOrder).Error; err == nil && hyBridgeOrder.ID > 0 {
		key := model.RDPrefixCosmosOrder + hyBridgeOrder.PayorderID
		if _, err := global.RD.SetAndExpire(key, hyBridgeOrder.PayorderID, 3000); err != nil {
			global.Log.Error("====>  redis SetString key_[%v] error：%v", key, err)
		}
		global.Log.Error("====>  GetCosmosChainTask set key_[%v] success", key)
		hyBridgeOrder.LockTimeStamp = strconv.Itoa(int(time.Now().Unix()))
		global.DB.Save(&hyBridgeOrder)

		return utils.SuccessNullMsg(c, hyBridgeOrder)
	} else {
		return utils.Success(c, "not any cosmoschain croos chain task", nil)
	}
}

// @Summary     获取跨链订单
// @Description 获取跨链订单
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Param       payorderid query    string true "跨链订单编号"
// @Param       timeStamp query    string true "时间戳"
// @Param       sign      query    string true "签名"
// @Success     200       {object} []model.HybridgeOrder
// @Failure     201     {object} string
// @Router      /crosschain/gethyorder [post]
func GetHyOrder(c echo.Context) error {
	if c.FormValue("payorderid") == "" {
		return utils.ErrParamType(c)
	}
	// signStr := "gethyorder timeStamp:" + c.FormValue("timeStamp")
	// if len(c.FormValue("sign")) != 130 || !verifySign(signStr, c.FormValue("sign")) {
	// 	return utils.SignValidFail(c)
	// }
	var hyBridgeOrder model.HybridgeOrder
	if err := global.DB.Model(model.HybridgeOrder{}).Where(" payorder_id = ?", c.FormValue("payorderid")).Find(&hyBridgeOrder).Error; err == nil && hyBridgeOrder.ID > 0 {
		return utils.SuccessNullMsg(c, hyBridgeOrder)
	} else {
		return utils.Error(c, "payorderid not exists", nil)
	}
}

// @Summary     获取跨链订单列表
// @Description 获取跨链订单列表
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Param       useradr   query    string true "用户钱包地址"
// @Param       pagesize  query    string true "分页大小"
// @Param       pageindex query    string true "分页索引"
// @Success     200       {object} model.PageData
// @Failure     400       {object} string
// @Router      /crosschain/gethyOrderlist [post]
func GetHyOrderList(c echo.Context) error {
	if c.FormValue("useradr") == "" {
		return utils.ErrParamType(c)
	}
	db := global.DB.Model(&model.HybridgeOrder{}).Where(" LOWER(sender)=?", strings.ToLower(c.FormValue("useradr"))).Order("insert_time desc")

	var count int64
	var list []model.HybridgeOrder
	db.Count(&count)
	pageIndex := utils.GetPageIndex(c.FormValue("pageindex"))
	pageSize := utils.GetPageSize(c.FormValue("pagesize"))
	if err := db.Limit(pageSize).Offset((pageIndex - 1) * pageSize).Scan(&list).Error; err != nil {
		global.Log.Error("GetHyOrderList error：%v", err.Error())
		return utils.ErrorNull(c, utils.GetFailResult)
	}
	return utils.SuccessNullMsg(c, &model.PageData{
		PageIndex:   pageIndex,
		PageSize:    pageSize,
		CurrentPage: pageIndex,
		PageNumber:  utils.GetPageNumber(count, pageSize),
		Count:       count,
		Data:        list,
	})
}

// @Summary     获取Gas费
// @Description 获取Gas费
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Param       chainid query    string true "链ID"
// @Success     200       {object} []model.HybridgeOrder
// @Failure     201       {object} string
// @Router      /crosschain/getchaingasFee [post]
func GetChainGasFee(c echo.Context) error {
	chainID, err := strconv.Atoi(c.FormValue("chainid"))
	if err != nil {
		return utils.ErrParamType(c)
	}
	if chainID == model.CosmosChainID {
		return utils.SuccessNullMsg(c, 0.00001)
	}

	return utils.SuccessNullMsg(c, getChainGasFee(chainID))
}

// @Summary     获取token价格
// @Description 获取token价格
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Param       tokenname query    string true "token名称"
// @Success     200       {object} string
// @Failure     201        {object} string
// @Router      /crosschain/gettokenprice [post]
func GetToeknPrice(c echo.Context) error {
	return utils.SuccessNullMsg(c, ether.GetTokenPrice(strings.ToLower(c.FormValue("tokenname"))))
}

func getChainGasFee(chainID int) float64 {
	_, client := ether.GetEtherClient(global.GetNewWorkInfo(chainID).RpcUrls)
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		global.Log.Error("====>  SuggestGasPrice error：%v", err.Error())
		return 0.0
	}
	currentBlock, err := client.HeaderByNumber(context.Background(), nil)
	if err != nil {
		global.Log.Error("====>  HeaderByNumber error: %v", err.Error())
		return 0.0
	}
	block, err := client.BlockByNumber(context.Background(), currentBlock.Number)
	if err != nil {
		global.Log.Error("====>  BlockByNumber error: %v", err.Error())
		return 0.0
	}
	client.Close()
	global.Log.Info("====>  gasPrice : %v", gasPrice)
	global.Log.Info("====>  block.GasLimit() : %v", block.GasLimit())
	gwei := gasPrice.Mul(gasPrice, big.NewInt(261*20000))
	gasPriceF := ether.BigintToFloat64(gwei)
	return gasPriceF
}

// @Summary     支持的NFT列表
// @Description 支持的NFT列表
// @Tags        api
// @Accept      application/json
// @Produce     json
// @Success     200 {object} []model.Contract
// @Failure     201     {object} string
// @Router      /crosschain/nftcontractlist [post]
func NftContractList(c echo.Context) error {
	var nftContractList []model.Contract
	db := global.DB.Model(&model.Contract{}).Order("insert_time DESC")
	if err := db.Scan(&nftContractList).Error; err != nil {
		global.Log.Error("NftContractList error：%v", err)
		return utils.ErrorNull(c, utils.GetFailResult)
	}
	return utils.SuccessNullMsg(c, nftContractList)
}

func makePayOrderId(nonce *big.Int, expTime uint32, srcChainId uint32, dstChainId uint32, action uint16, flag uint16) (payOrderId *big.Int) {
	client, err := ethclient.Dial(global.Conf.RunParams.RpcUrl)
	if err != nil {
		global.Log.Error("====>  ethclient.Dial error：%v", err.Error())
		return
	}
	defer client.Close()

	botManager, _ := contract.NewBotManager(common.HexToAddress(global.Conf.RunParams.BotManager), client)
	payOrderId, err = botManager.GenCode(nil, nonce, expTime, srcChainId, dstChainId, action, flag)
	if _, err := global.RD.SetString(payOrderId.String(), "true"); err != nil {
		global.Log.Error("====>  redis SetString key_[%v] error：%v", "payorderids", err)
	}
	if err != nil {
		global.Log.Error("====>  makePayOrderId error：%v", err.Error())
		return
	}
	return payOrderId
}

func getHandingEvmTask() (handingOrderIds string) {
	hyOrderKeys, err := global.RD.Keys("*" + model.RDPrefixEvmOrder + "*")
	if err != nil {
		global.Log.Error("====>  redis GetString EvmOrderInfo_ error： %s", err.Error())
		return
	}
	for _, payorderId := range hyOrderKeys {
		handingOrderIds += strings.Replace(payorderId, model.RDPrefixEvmOrder, "", -1) + ","
	}
	if len(handingOrderIds) > 0 {
		return handingOrderIds[:len(handingOrderIds)-1]
	}
	return ""
}

func getHandingCosmosTask() (handingOrderIds string) {
	hyOrderKeys, err := global.RD.Keys("*" + model.RDPrefixCosmosOrder + "*")
	if err != nil {
		global.Log.Error("====>  redis GetString CosmosOrderInfo_ error： %s", err.Error())
		return
	}
	for _, payorderId := range hyOrderKeys {
		handingOrderIds += strings.Replace(payorderId, model.RDPrefixCosmosOrder, "", -1) + ","
	}
	return handingOrderIds[:len(handingOrderIds)-1]
}

func verifySign(signStr string, signature string) bool {
	data := []byte(signStr)
	hash := crypto.Keccak256Hash(ether.EncodePacked(data))
	sig, err := ether.HexDecode(signature)
	if err != nil {
		global.Log.Error("====>  VerifySign error： %s", err.Error())
	}
	sig[64] -= 27
	fmt.Println(hexutil.Encode(sig))
	sigPublicKeyECDSA, err := crypto.SigToPub(hash.Bytes(), sig)
	if err != nil {
		log.Fatal(err)
	}
	signAdr := crypto.PubkeyToAddress(*sigPublicKeyECDSA)

	client, err := ethclient.Dial(global.Conf.RunParams.RpcUrl)
	if err != nil {
		global.Log.Error("====>  verifySign ethclient.Dial error：%v", err.Error())
		return false
	}
	defer client.Close()
	botManager, _ := contract.NewBotManager(common.HexToAddress(global.Conf.RunParams.BotManager), client)
	canSign, err := botManager.HandleBotRole(nil, signAdr)
	if err != nil {
		global.Log.Error("====>  verifySign get HandleBotRole error：%v", err.Error())
		return false
	}

	return canSign
}
